package mock

import (
	"encoding/json"
	"io/ioutil"
	"time"

	"rt.com/mocktool/utils/guid"

	"gopkg.in/yaml.v2"
	"rt.com/mocktool/utils/db"
	"rt.com/mocktool/utils/file"
	"rt.com/mocktool/utils/glog"
	"rt.com/mocktool/values"
)

// Instance Mock包实例
var Instance = &Mock{}

// Mock MockTool数据核心
type Mock struct {
	dbInterface *db.DB  // 数据库实例
	config      *Config // 服务配置文件映射
	configPath  string  // 服务配置文件路径

	MethodList map[string]MethodDetail // ID与方法映射表
	URLMap     map[string][]string     // 地址与ID映射表
}

// Init 初始化核心库
func (mock *Mock) Init(configPath string) {
	// 输出业务核心开始初始化调试日志
	glog.Debug(values.TextMockIniting)
	// 加载配置文件
	mock.configPath = configPath
	if mock.configPath == "" {
		mock.configPath = "default.yml"
	}
	if !file.Exist(mock.configPath) {
		mock.createNewConfig()
	} else {
		mock.loadConfig()
	}
	// 关闭全局拦截默认为false
	mock.config.GlobalInactive = 0
	// 数据库文件绝对路径
	dbFileAbsolte := file.PathInExePath(mock.config.DBFile)
	glog.Debug(values.TextMockConfigAdmin, mock.config.AdminPort)
	glog.Debug(values.TextMockConfigProxy, mock.config.ProxyPort)
	glog.Debug(values.TextMockConfigDBFile, dbFileAbsolte)
	// 打开数据库
	mock.dbInterface = &db.DB{}
	err := mock.dbInterface.Open(dbFileAbsolte)
	if err != nil {
		// 数据库打开异常
		glog.Error(values.TextMockOpenDBFailed, err)
	}
	// 加载数据表
	mock.LoadDB()
	// 输出业务核心初始化完毕调试日志
	glog.Info(values.TextMockInited)
}

// Close 关闭核心数据
func (mock *Mock) Close() {
	if mock.dbInterface != nil {
		err := mock.dbInterface.Close()
		if err != nil {
			glog.Error(err)
		}
	}
}

// loadConfig 加载配置文件
func (mock *Mock) loadConfig() {
	glog.Debug(values.TextMockLoadConfig, values.Return, values.LineSpace, mock.configPath)
	data, err := ioutil.ReadFile(mock.configPath)
	if err != nil || data == nil || len(data) == 0 {
		glog.Error(values.TextMockNewConfigFormat)
		mock.createNewConfig()
	} else {
		err = yaml.Unmarshal(data, &mock.config)
		if err != nil {
			glog.Error(err)
		}
	}
	glog.Debug(values.TextMockLoadConfigEnd)
}

// createNewConfig 创建配置文件
func (mock *Mock) createNewConfig() {
	glog.Debug(values.TextMockNewConfigStart, values.Return, values.LineSpace, mock.configPath)
	mock.config = &Config{
		AdminPort: values.PortAdmin,
		ProxyPort: values.PortProxy,
		DBFile:    values.DBFile,
	}
	err := mock.UploadConfig()
	if err != nil {
		glog.Error(values.TextMockNewConfigFailed, values.Return, values.LineSpace, err.Error())
	}
	glog.Debug(values.TextMockNewConfigEnd)
}

// UploadConfig 更新配置文件
func (mock *Mock) UploadConfig() error {
	data, err := yaml.Marshal(&mock.config)
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(mock.configPath, data, 0600)
	if err != nil {
		return err
	}
	return nil
}

// GetConfig 获取配置
func (mock *Mock) GetConfig() *Config {
	return mock.config
}

// LoadDB 加载DB数据到本地
func (mock *Mock) LoadDB() {
	mock.MethodList = make(map[string]MethodDetail)
	mock.URLMap = make(map[string][]string)
	// 读取ID与方法映射表
	err := mock.dbInterface.ForEach([]byte("MethodList"), func(key, value []byte) error {
		item := MethodDetail{}
		e := json.Unmarshal(value, &item)
		if e != nil {
			glog.Debug(string(key), "=", string(value))
			return e
		}
		mock.MethodList[string(key)] = item
		return nil
	})
	if err != nil {
		glog.Error(err)
	}
	// 读取URL与ID映射表
	if len(mock.MethodList) > 0 {
		for _, method := range mock.MethodList {
			if mock.URLMap[method.Name] == nil {
				mock.URLMap[method.Name] = make([]string, 0)
			}
			mock.URLMap[method.Name] = append(mock.URLMap[method.Name], method.ID)
		}
	}
}

// UpdateMethod 更新或插入Method
func (mock *Mock) UpdateMethod(item MethodDetail) {
	if item.ID == "" {
		// 新建item
		item.ID = guid.Get()
		// 获取系统时间戳
		item.TIME = time.Now().Unix()
	}
	err := mock.dbInterface.Update([]byte("MethodList"), []byte(item.ID), item)
	if err != nil {
		glog.Error(err)
	} else {
		mock.LoadDB()
	}
}

// DeleteMethod 删除方法
func (mock *Mock) DeleteMethod(methods []MethodItem) {
	if methods == nil || len(methods) < 0 {
		return
	}
	for _, v := range methods {
		mock.dbInterface.Delete([]byte("MethodList"), []byte(v.ID))
	}
	mock.LoadDB()
}

// CopyMethod 复制方法
func (mock *Mock) CopyMethod(methods []MethodItem) {
	if methods == nil || len(methods) < 0 {
		return
	}
	for _, v := range methods {
		detail, exist := mock.MethodList[v.ID]
		if exist {
			detail.ID = guid.Get()
			err := mock.dbInterface.Update([]byte("MethodList"), []byte(detail.ID), detail)
			if err != nil {
				glog.Error(err)
			}
		}
	}
	mock.LoadDB()
}
